# -*- coding: utf-8 -*-
import log, pttimers
import hall_glbs, hall_object, hall_errcode, hall_callback
import struct, random
import td_const, td_error, td_cmd
import td_room_player_s


#处理房间逻辑的类，要求继承hall_object.HallRoom，从进入游戏房间到开始游戏前，准备界面的逻辑基本上在这个类里面处理
class CRoomLogic(hall_object.HallRoom):
	def __init__(self, room_id = 0, name = "", mode = 0, host = 0, pwd = "", max_num=1, can_match=False):
		super(CRoomLogic, self).__init__(room_id, name, mode, host, pwd, max_num, can_match)
		self.dicSidePlayers = {}			#自定义玩家标志：房间内玩家对象的字典
		self.dicUidPlayers = {}				#玩家uid：房间内玩家对象的字典
		self.dicHidPlayers = {}				#玩家hid：房间内玩家对象的字典
		self.bIsCountDown = False			#是否处于倒数状态的标志位
		self.iCountDown = td_const.WAIT_COUNTDOWN * td_const.FPS_SERVER		#倒数剩余时间
		self.iStatus = td_const.GAME_WAIT		#房间状态（准备/游戏初始化/游戏中）
		
		#注册定时器
		#self.objTimer = pttimers.CTimer(seconds=float(1)/td_const.FPS_SERVER, callback=self.on_timer, forever=True)
		#self.objTimer.start()
		
		self.iStage = None				#当前选中的关卡
		self.iMaster = None				#当前的房主的玩家标识
		self.objMsgMgr = hall_callback.get_game_room_msgmgr()		#传输对象的引用，消息打包的时候用到
		#self.objGameLogic = td_mapmgr_s.CMapMgr(self)			#处理游戏逻辑的类
		#日志记录房间创建相关信息
		self.cglog(td_const.LOGTYPE_KEYPOINT, "[NEW_ROOM][id=%s;name=%r;mode=%s;max_player=%s;can_match=%s]" % (room_id, name, mode, max_num, can_match))
	
	#获取空余可用的玩家标识
	def get_empty_side(self):
		iSide = None
		for i in xrange(self.max_num):
			if not self.dicSidePlayers.has_key(i):
				iSide = i
				break
		return iSide
		
	#基类的虚函数，当玩家从大厅登陆进某个房间的时候，会调用这个函数，_player是大厅中的玩家对象
	def cghall_on_player_enter_room(self, _player, obj):
		#只有房间处于准备状态下，才接受新玩家的进入
		if self.iStatus != td_const.GAME_WAIT:
			return td_error.ERROR_ENTER_ROOM_STARTED
		#获取空余的玩家标识，如果获取不到，则拒绝此玩家登陆房间
		iSide = self.get_empty_side()
		if iSide is None:
			return td_error.ENTER_ROOM_NO_SEAT

		#生成房间内玩家对象
		objPlayer = td_room_player_s.CPlayer(self, _player, iSide)
		#先把玩家加入玩家列表（因为底层接口不允许对玩家列表外的玩家发送消息）
		self.add_player(objPlayer)
		#日志记录玩家登陆
		self.cglog(td_const.LOGTYPE_KEYPOINT, "[PLAYER_ENTER][uid=%s;side=%s]" % (objPlayer.uid, objPlayer.iSide))
		#第一个进入的玩家自动成为房主，并且当前关卡切换为房主的选中关卡
		if self.iMaster is None:
			self.iMaster = objPlayer.iSide
			self.iStage = objPlayer.iSelectStage
			
		return td_error.SUCCESS
	
	#基类的虚函数，当玩家从某个房间退出的时候，会调用这个函数，hid是玩家的socket
	def cghall_on_player_leave_room(self, hid):
		#获取要退出的玩家的对象
		objPlayer = self.cghall_get_player_by_hid(hid)
		if not objPlayer:
			return td_error.LEAVE_ROOM_NO_PLAYER
		return td_error.SUCCESS
		
	#重载基类的函数，在房间对象被销毁前，大厅逻辑会调用这个函数，需要在这个函数里清除自己创建的各种引用，释放内存，避免出现内存泄漏
	def cghall_clean(self):
		super(CRoomLogic, self).cghall_clean()
		#调用游戏逻辑的清理函数并清除对游戏逻辑对象的引用
		self.objGameLogic.clean()
		self.objGameLogic = None
		#清除对传输对象的引用
		self.objMsgMgr = None
		#遍历玩家列表，调用每个玩家对象的清理函数
		for objPlayer in self.dicSidePlayers.itervalues():
			objPlayer.clean()
		#清空管理玩家对象的三个字典
		self.dicSidePlayers.clear()
		self.dicUidPlayers.clear()
		self.dicHidPlayers.clear()
		#中止并清理timer
		self.objTimer.stop()
		self.objTimer.delete()
		self.objTimer = None
		#日志记录房间自清理完毕，等待释放
		self.cglog(td_const.LOGTYPE_KEYPOINT, "[DEL_ROOM]")
		
	#在房间类中创建的timer的回调函数，每个游戏帧调用一次
	def on_timer(self):
		#房间在准备状态
		if self.iStatus == td_const.GAME_WAIT:
			#检测是否处于倒数状态
			if not self.bIsCountDown:
				return
			self.iCountDown -= 1
			#倒数时间到则进入游戏初始化状态
			if self.iCountDown <= 0:
				self.on_game_init()
		#房间在游戏初始化状态
		elif self.iStatus == td_const.GAME_INIT:
			pass
		#房间在游戏中状态，直接调用游戏逻辑类的帧处理函数
		elif self.iStatus == td_const.GAME_PLAYING:
			self.objGameLogic.logic_frame()
			
	#房间进入游戏初始化状态时调用的函数
	def on_game_init(self):
		#遍历当前玩家对象，重置ready标志位并调用玩家对象的游戏初始化函数
		for objPlayer in self.dicSidePlayers.itervalues():
			objPlayer.bIsReady = False
			objPlayer.game_init()
		#结束倒数
		self.bIsCountDown = False
		self.iCountDown = td_const.INIT_COUNTDOWN * td_const.FPS_SERVER
		#房间状态标识切换
		self.iStatus = td_const.GAME_INIT
		#调用基类的函数，通知大厅游戏已经开始了（大厅就不会再把新玩家塞进房间了）
		self.cghall_tell_hall_game_start()
		#向客户端广播游戏进入初始化阶段的消息
		self.send_game_init()
		#日志记录游戏状态切换，进入游戏初始化阶段
		self.cglog(td_const.LOGTYPE_KEYPOINT, "[ENTER_GAME_INIT][stage=%s]" % self.iStage)
		
	#房间进入游戏开始状态时调用的函数
	def on_game_start(self):
		#遍历当前玩家对象，重置是否完成初始化的标志位，调用玩家对象的游戏开始函数，并生成加入游戏的玩家的列表
		_list = []
		for objPlayer in self.dicSidePlayers.itervalues():
			objPlayer.bIsInit = False
			objPlayer.game_start()
			_list.append(objPlayer)
		#结束倒数
		self.bIsCountDown = False
		#房间状态标识切换
		self.iStatus = td_const.GAME_PLAYING
		#再次调用基类的函数，通知大厅游戏已经开始了（某种情况下（传送带模式）会跳过游戏初始化状态，直接从准备状态进入游戏中状态）
		self.cghall_tell_hall_game_start()
		#调用游戏逻辑类的游戏开始函数，参数是关卡id和加入游戏的玩家列表
		self.objGameLogic.on_game_start(self.iStage, _list)
		#日志记录游戏状态切换，进入游戏中状态
		self.cglog(td_const.LOGTYPE_KEYPOINT, "[ENTER_GAME_PLAYING][stage=%s]" % self.iStage)
	
	#房间从游戏中状态返回准备状态时调用的函数
	def on_game_end(self, win, player_list):
		#调用基类的函数，通知大厅游戏已经结束，可以放新玩家进房间了
		self.cghall_tell_hall_game_end()
		#房间状态标识切换
		self.iStatus = td_const.GAME_WAIT
		#遍历游戏逻辑类发送过来的游戏中玩家对象列表，调用房间内玩家对象的游戏结算函数，保存需要保存的结算信息
		for game_player in player_list:
			room_player = self.get_player_by_side(game_player.iSide)
			if room_player:
				room_player.game_end(self.iStage, game_player)
		#房间内玩家对象状态ready标志位重置为False
		for objPlayer in self.dicSidePlayers.itervalues():
			objPlayer.bIsReady = False			
		#向客户端广播游戏结算的消息
		self.send_game_end(win, player_list)
		#日志记录游戏状态切换，返回准备状态
		self.cglog(td_const.LOGTYPE_KEYPOINT, "[ENTER_GAME_WAIT][win=%s]" % win)
	
	#检测是否满足倒数条件的函数
	def can_countdown(self):
		total = len(self.dicSidePlayers)
		if total <= 0:
			return False
		ready = 0
		for objPlayer in self.dicSidePlayers.itervalues():
			if objPlayer.bIsReady:
				ready += 1
		if (total - ready) <= ready:
			return True
		else:
			return False
	
	#检测倒数状态是否存在切换，并发送倒数开始/停止消息的函数
	def check_countdown(self):
		flag = self.can_countdown()
		if self.bIsCountDown == flag:
			return
		if self.bIsCountDown and (not flag):
			self.bIsCountDown = flag
			self.send_countdown(False)
			#结束倒数后允许配对/新玩家进入
			self.cghall_tell_hall_game_end()
		elif (not self.bIsCountDown) and flag:
			self.bIsCountDown = flag
			self.iCountDown = td_const.WAIT_COUNTDOWN * td_const.FPS_SERVER
			self.send_countdown(True)
			#开始倒数后拒绝配对/新玩家进入
			self.cghall_tell_hall_game_start()
	
	#检测是否满足立刻进入游戏初始化状态的函数（所有玩家都ready）
	def check_game_init(self):
		total = len(self.dicSidePlayers)
		if total <= 0:
			return False
		ready = 0
		for objPlayer in self.dicSidePlayers.itervalues():
			if objPlayer.bIsReady:
				ready += 1
		if total == ready:
			return True
		else:
			return False
	
	#检测是否满足进入游戏中状态的函数（所有玩家都发送了初始化完成（选择了带入游戏的植物卡片）的消息）
	def check_game_start(self):
		total = len(self.dicSidePlayers)
		if total <= 0:
			return False
		ready = 0
		for objPlayer in self.dicSidePlayers.itervalues():
			if objPlayer.bIsInit:
				ready += 1
		if total == ready:
			return True
		else:
			return False
	
	#通过玩家标识获取玩家对象的函数
	def get_player_by_side(self, iSide):
		return self.dicSidePlayers.get(iSide, None)
	
	#重载了基类的同名函数，让响应客户端发送过来的消息的回调函数中的objplayer参数是游戏自定义的玩家对象，而不是大厅的玩家对象
	def cghall_get_player_by_uid(self, uid):
		return self.dicUidPlayers.get(uid, None)
	
	#重载了基类的同名函数，让响应客户端发送过来的消息的回调函数中的objplayer参数是游戏自定义的玩家对象，而不是大厅的玩家对象
	def cghall_get_player_by_hid(self, hid):
		return self.dicHidPlayers.get(hid, None)
	
	#此函数随机获取一个玩家对象，目前只有切换房主的时候在用
	def get_random_player(self):
		lst = self.dicSidePlayers.values()
		if lst:
			return random.choice(lst)
		else:
			return None
	
	#此函数把一个玩家对象添加到管理玩家对象的三个字典中
	def add_player(self, objPlayer):
		self.dicSidePlayers[objPlayer.iSide] = objPlayer
		self.dicUidPlayers[objPlayer.uid] = objPlayer
		self.dicHidPlayers[objPlayer.hid] = objPlayer
	
	#此函数调用玩家对象的自清理函数并把这个玩家对象从管理玩家对象的三个字典中删除
	def del_player(self, objPlayer):
		objPlayer.clean()
		if self.dicSidePlayers.has_key(objPlayer.iSide):
			del self.dicSidePlayers[objPlayer.iSide]
		if self.dicUidPlayers.has_key(objPlayer.uid):
			del self.dicUidPlayers[objPlayer.uid]
		if self.dicHidPlayers.has_key(objPlayer.hid):
			del self.dicHidPlayers[objPlayer.hid]
			
	#-------------------------------------------------------------------------------------------------------------------------------------------
	##以下是接收并处理客户端消息的函数
	#此函数处理客户端发送的游戏准备/取消准备消息
	def c_game_change_score():
		pass
	def c_game_ready(self, objPlayer, obj):
		#只有在准备状态下才接收这类消息
		if self.iStatus != td_const.GAME_WAIT:
			return
		bIsReady = obj.is_ready
		#状态没有变化，直接return
		if objPlayer.bIsReady == bIsReady:
			return
		#切换玩家对象中的ready标志位
		objPlayer.bIsReady = bIsReady
		#向客户端广播准备状态发生变化的消息
		self.send_change_ready(objPlayer, bIsReady)
		#调用基类的函数，通知大厅某个玩家的准备状态发生了变化
		self.cghall_tell_hall_player_ready(objPlayer.hid, bIsReady)
		#检测是否满足立刻进入游戏初始化阶段
		if self.check_game_init():
			self.on_game_init()
		#检测是否满足倒数条件
		else:
			self.check_countdown()
	
	#此函数处理客户端发送的游戏初始化消息（选择带入游戏的植物卡片）
	def c_game_init(self, objPlayer, obj):
		#只有在游戏初始化状态下才接收此类消息
		if self.iStatus != td_const.GAME_INIT:
			return
		#单局游戏单个玩家只能初始化一次
		if objPlayer.bIsInit:
			return
		#调用玩家对象的初始化函数，保存带入游戏的卡片列表并切换状态标识
		objPlayer.on_game_init(obj.card_list)
		#满足游戏立刻开始条件（所有玩家都初始化完成），则开始游戏
		if self.check_game_start():
			self.on_game_start()
	
	#此函数处理客户端发送的选择关卡消息
	def c_select_stage(self, objPlayer, obj):
		#只有准备状态下才接收这类消息
		if self.iStatus != td_const.GAME_WAIT:
			return
		#非房主无权选择关卡
		if objPlayer.iSide != self.iMaster:
			return
		stage = obj.stage
		#检测此玩家是否有选择这个关卡的条件
		if objPlayer.check_select_stage(stage):
			self.iStage = stage
			#向客户端广播当前关卡变化的消息
			self.send_change_stage(stage)
	
	#此函数处理客户端发送的退出游戏房间的消息
	def c_leave_game(self, objPlayer, obj):
		self.cghall_tell_hall_player_leave_room(objPlayer.hid)
	
	#此函数处理客户端发送的游戏中子事件的消息
	def c_game_event(self, objPlayer, obj):
		#只有游戏中才接收此类消息
		if self.iStatus != td_const.GAME_PLAYING:
			return
		#调用游戏逻辑类的处理函数
		self.objGameLogic.on_game_event(objPlayer.iSide, obj.frame, obj.event_id, obj.param)
	
	#此函数处理客户端发送的使用巫师指令的消息
	def c_wizard(self, objPlayer, obj):
		#只有调试状态下才接收此类消息
		if not hall_callback.is_debug():
			self.cglog(td_const.LOGTYPE_ERROR, "[USE_WIZRAD_ERROR][uid=%s]" % (objPlayer.uid, ))
			return
		if self.iStatus == td_const.GAME_PLAYING:
			self.objGameLogic.on_wizard(objPlayer.iSide, obj)
	#-------------------------------------------------------------------------------------------------------------------------------------------
	##以下是打包并发送消息的函数
	#发送消息给所有人
	def send_all(self, obj, exclude = []):
		self.cghall_broadcast(obj, exclude)
			
	#发送消息给某人
	def send_to_user(self, objPlayer, obj):
		self.cghall_send(objPlayer.hid, obj)
	
	#发送游戏模式
	def send_game_mode(self, target):
		obj = self.objMsgMgr.game_s_game_mode(mode=self.game_mode, stage=self.iStage, master=self.iMaster)
		self.send_to_user(target, obj)
	
	#发送玩家进入消息-参数：目标玩家，新进入的玩家
	def send_user_in(self, target, objPlayer):
		obj = self.objMsgMgr.game_s_user_in_game(side=objPlayer.iSide, urs=objPlayer.urs, \
													uid=objPlayer.uid, nickname=objPlayer.nickname, \
													is_ready=objPlayer.bIsReady, gmlvl=objPlayer.gmlvl)
		self.send_to_user(target, obj)
		#发送玩家的具体信息 
		obj = self.objMsgMgr.game_s_player_detail_info(side=objPlayer.iSide, score=objPlayer.score, _win=objPlayer.win_count, \
														_lose=objPlayer.lose_count, _draw=objPlayer.draw_count, _break=objPlayer.break_count, \
														avatar=objPlayer.avatar, pet=objPlayer.pet)
		self.send_to_user(target, obj)
	
	#发送玩家退出消息
	def send_user_out(self, objPlayer):
		obj = self.objMsgMgr.game_s_user_out_game(side=objPlayer.iSide, )
		self.send_all(obj, [objPlayer.hid, ])
	
	#发送玩家准备/取消准备消息
	def send_change_ready(self, objPlayer, bIsReady):
		obj = self.objMsgMgr.game_s_game_ready(side=objPlayer.iSide, is_ready=bIsReady)
		self.send_all(obj)
	
	#发送玩家选择卡片消息
	def send_game_init(self):
		obj = self.objMsgMgr.game_s_game_init(stage=self.iStage)
		self.send_all(obj)
	
	def send_change_stage(self, iStage):
		obj = self.objMsgMgr.game_s_select_stage(stage=iStage)
		self.send_all(obj)
	
	#发送倒数/取消倒数消息
	def send_countdown(self, bIsCountDown):
		obj = self.objMsgMgr.game_s_countdown(is_countdown=bIsCountDown, )
		self.send_all(obj)
	
	#发送游戏结束消息
	def send_game_end(self, iWin, player_list):
		_list = []
		for game_player in player_list:
			obj = self.objMsgMgr.game_player_end_info(side=game_player.iSide, rank=game_player.iRank, score=game_player.iScore, killed=game_player.iKillMonsterNum)
			_list.append(obj)
		obj = self.objMsgMgr.game_s_game_end(win=iWin, stage=self.iStage, player_list=_list)
		self.send_all(obj)
	
	#发送游戏币，道具信息
	def send_player_secret_info(self, objPlayer):
		_list = []
		for iStage in objPlayer.lstFinishStage:
			int_obj = self.objMsgMgr.game_single_int(value=iStage)
			_list.append(int_obj)
		achieve_list = []
		for iID in objPlayer.lstFinishAchieveID:
			int_obj = self.objMsgMgr.game_single_int(value=iID)
			achieve_list.append(int_obj)
		obj = self.objMsgMgr.game_s_player_secret_info(yuanbao=objPlayer.yuanbao, yuanbao_free=objPlayer.yuanbao_free, finish_stage=_list, finish_achieve=achieve_list)
		self.send_to_user(objPlayer, obj)

	#-------------------------------------------------------------------------------------------------------------------------
	##以下是成就系统	
	def cghall_on_add_achievement_result(self, hid, uid, tag, ok, achid):
		#日志记录平台对提交成就消息的异步返回到达
		self.cglog(td_const.LOGTYPE_KEYDATA, "[COMMON_DATA][ADD_ACHIEVE_RETURN][uid=%s;hid=%s;ok=%s;achid=%s]" % (uid, hid, ok, achid))
		#成功失败暂不做处理，等待下次达到条件再保存
		if ok:
			objPlayer = self.cghall_get_player_by_uid(uid)
			if not objPlayer:
				return
			#刷新成就的id列表
			objPlayer.update_achieve()
			obj = self.objMsgMgr.game_s_player_finish_achieve(achieve_id=achid)
			self.send_to_user(objPlayer, obj)
	#-------------------------------------------------------------------------------------------------------------------------
	##以下是日志相关
	def cglog(self, iType, text):
		text = td_const.LOG_KEYWORD_DIC[iType] + text
		if iType == td_const.LOGTYPE_DEEP:
			self.log.deep(text)
		elif iType == td_const.LOGTYPE_DEBUG:
			self.log.debug(text)
		elif iType == td_const.LOGTYPE_KEYPOINT:
			self.log.info(text)
		elif iType == td_const.LOGTYPE_KEYDATA:
			self.log.info(text)
		elif iType == td_const.LOGTYPE_DESIGN:
			self.log.warn(text)
		elif iType == td_const.LOGTYPE_CONFIG:
			self.log.warn(text)
		elif iType == td_const.LOGTYPE_ERROR:
			self.log.error(text)
	
